using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Navigation;
using Columbus.Controls;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;

namespace Columbus
{
    public class NavigationAppication : INavigationApplication
    {
        private readonly IEnumerable<INavigator> applicationNavigators;
        private readonly ITransientStore transientStore;
        private readonly Action<INavigator> defaultAction;
        private static readonly IApplicationSettings Settings;

        static NavigationAppication()
        {
            var applicationSettings = new ApplicationSettings();
            applicationSettings.Restore();
            Settings = applicationSettings;
        }

        public const string ShellFileIdentifier = "NavigationApplicationShell";

        public bool InitializingAfterTombstoning { get; private set; }

        public PhoneApplicationPage InitializedShell { get; private set; }

        public static IApplicationSettings ApplicationSettings
        {
            get { return Settings; }
        }

        public NavigationAppication(
            IEnumerable<INavigator> applicationNavigators, 
            ITransientStore transientStore,
            Action<INavigator> defaultAction = null)
        {
            if (applicationNavigators == null) throw new ArgumentNullException("applicationNavigators");
            if (!applicationNavigators.Any()) throw new ArgumentException("applicationNavigators collection can not be empty");
            
            this.applicationNavigators = applicationNavigators;
            this.transientStore = transientStore;
            this.defaultAction = defaultAction;
        }

        public void Activated(object sender, ActivatedEventArgs args)
        {
            InitializingAfterTombstoning = true;
        }

        public void StatusChanged(object sender, NavigationEventArgs args)
        {
            var shell = args.Content as PhoneApplicationPage;
            var tombstoneRequested = args.Content == null;
            var startingNormally = !tombstoneRequested && !InitializingAfterTombstoning;

            if (!tombstoneRequested && shell == null)
                throw new Exception("Shell must be PhoneApplicationPage");
            
            if (startingNormally)
            {
                transientStore.Remove(String.Format(ViewState.KeyPattern, "*"));
                transientStore.Remove(String.Format(Journal.JournalKeyPattern, "*"));
                transientStore.Remove(String.Format(Journal.JournalEntryKeyPattern, "*"));
            }

            foreach (var navigator in applicationNavigators)
            {
                if (tombstoneRequested)
                {
                    navigator.Deactivate(transientStore);
                }
                else
                {
                    Initialize(navigator, shell, transientStore);

                    if (InitializingAfterTombstoning)
                    {
                        navigator.Activate(transientStore, ApplicationSettings);
                    }
                }
            }

            if (!tombstoneRequested)
                InitializeShell(shell);
            else
                DeactivateShell(InitializedShell);
        }

        private void DeactivateShell(PhoneApplicationPage shell)
        {
            ((ApplicationSettings)ApplicationSettings).Persist();
            if (shell == null) return;
            var shellViewModel = shell.DataContext as ViewModel;
            if (shellViewModel == null) return;

            var viewState = new ViewState("Shell", ShellFileIdentifier, shellViewModel.GetType().FullName);
            shellViewModel.Deactivate(viewState);
            viewState.Persist(transientStore);
        }

        private void InitializeShell(PhoneApplicationPage shell)
        {
            var isSingleFrameApp = applicationNavigators.Count() == 1;

            if (shell == null) return;

            var appAwareShell = shell.DataContext as ShellViewModelBase;
            if (appAwareShell != null)
                appAwareShell.NavigationApplication = this;

            var shellViewModel = shell.DataContext as ViewModel;

            if (shellViewModel != null && shellViewModel.Navigator == null && isSingleFrameApp)
                shellViewModel.Navigator = GetNavigator();

            if (shellViewModel != null && InitializingAfterTombstoning)
                shellViewModel.Activate(transientStore.Load<ViewState>(String.Format(ViewState.KeyPattern, ShellFileIdentifier)), ApplicationSettings);

            if (shell == InitializedShell) return;

            InitializedShell = shell;
            if (isSingleFrameApp)
            {
                var singleNavigator = applicationNavigators.Single();
                InitializedShell.BackKeyPress += (x, e) =>
                {
                    if (singleNavigator.NavigateBack())
                        e.Cancel = true;
                };

                if (!InitializingAfterTombstoning && defaultAction != null)
                    defaultAction(singleNavigator);
            }
            else
            {
                var goBackIntegratedNavigators = applicationNavigators.Where(n => n.NavigationFrame.IsGoBackButtonIntegrated).ToArray();
                if (goBackIntegratedNavigators.Any())
                {
                    InitializedShell.BackKeyPress += (x, e) =>
                    {
                        if (goBackIntegratedNavigators.Any(n => n.NavigateBack()))
                            e.Cancel = true;
                    };
                }
            }
        }

        private static void Initialize(INavigator navigator, PhoneApplicationPage shell, ITransientStore transientStore)
        {
            var navigationFrame = shell.FindName(navigator.NavigationFrameName) as Frame;
            if (navigationFrame == null)
                throw new NavigationFrameNotFoundException(navigator.NavigationFrameName);
            navigator.Initialize(navigationFrame, transientStore);
        }

        public INavigator GetNavigator()
        {
            var navigator = applicationNavigators.SingleOrDefault(n => n.NavigationFrameName == Navigator.DefaultNavigationFrameName);
            if (navigator == null)
                throw new NavigatorNotFoundException(Navigator.DefaultNavigationFrameName);

            return navigator;
        }

        public INavigator GetNavigator(string navigationFrameName)
        {
            if (String.IsNullOrEmpty(navigationFrameName)) throw new ArgumentNullException("navigationFrameName");

            var navigator = applicationNavigators.SingleOrDefault(n => n.NavigationFrameName == navigationFrameName);
            if (navigator == null)
                throw new NavigatorNotFoundException(navigationFrameName);

            return navigator;
        }

        /// <summary>
        /// Creates navigation application with single navigation frame.
        /// </summary>
        /// <param name="controllerLocator">Controller locator is function that should return controller instance by name.</param>
        /// <param name="viewLocator">View locator is function that should return view instance by name.</param>
        /// <param name="defaultAction">Default action of single navigator can be used to navigate to default page when application starts.</param>
        /// <returns>Navigation application</returns>
        public static NavigationAppication CreateSingleFrameApplication(
            Func<string, Controller> controllerLocator,
            Func<string, FrameworkElement> viewLocator,
            Action<INavigator> defaultAction = null)
        {
            return new NavigationAppication(new[] { new Navigator(controllerLocator, viewLocator) }, new TransientStore(), defaultAction);
        }
    }
}